package com.anji.plus.gaea.job.executor.service;

import com.anji.plus.gaea.job.executor.config.ExecutorProperties;
import com.anji.plus.gaea.job.executor.handler.JobHandler;
import com.anji.plus.gaea.job.executor.thread.JobThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Created by xuxueli on 2016/3/2 21:14.
 *
 * Borrowed from xxljob v2.4.0
 */
public class XxlJobExecutor  {
    private static final Logger logger = LoggerFactory.getLogger(XxlJobExecutor.class);

    // ---------------------- param ----------------------
    protected ApplicationContext applicationContext;

    protected ExecutorProperties executorProperties;

    public void destroyJobThread(){

        // destroy jobThreadRepository
        if (jobThreadRepository.size() > 0) {
            for (Map.Entry<Long, JobThread> item: jobThreadRepository.entrySet()) {
                JobThread oldJobThread = removeJobThread(item.getKey(), "web container destroy and kill the job.");
                // wait for job thread push result to callback queue
                if (oldJobThread != null) {
                    try {
                        oldJobThread.join();
                    } catch (InterruptedException e) {
                        logger.error(">>>>>>>>>>> xxl-job, JobThread destroy(join) error, jobId:{}", item.getKey(), e);
                    }
                }
            }
            jobThreadRepository.clear();
        }
        jobHandlerRepository.clear();
    }



    //*********************** TriggerClient *************************
    private static List<TriggerClient> triggerClientList;
    protected void initTriggerClientList(ExecutorProperties executorProperties) {
        String triggerAddresses = executorProperties.getTriggerAddresses();
        String triggerToken = executorProperties.getAccessToken();
        if(triggerAddresses == null || triggerAddresses.trim().length() == 0){
            return;
        }
        for (String address: triggerAddresses.trim().split(",")) {
            if (address==null || address.trim().length()==0) {
                continue;
            }
            TriggerClient triggerClient = new TriggerClient(address, triggerToken);
            if(triggerClientList == null){
                triggerClientList = new ArrayList<TriggerClient>();
            }
            triggerClientList.add(triggerClient);
        }
    }
    public static List<TriggerClient> getTriggerClientList() {
        return triggerClientList;
    }

    // ---------------------- job handler repository ----------------------
    private static ConcurrentMap<String, JobHandler> jobHandlerRepository = new ConcurrentHashMap<String, JobHandler>();
    public static JobHandler loadJobHandler(String name){
        return jobHandlerRepository.get(name);
    }

    protected void registJobHandler(){
        String[] jobHandlerBeanNames = applicationContext.getBeanNamesForType(JobHandler.class, false, true);
        if(jobHandlerBeanNames == null || jobHandlerBeanNames.length ==0){
            logger.warn("XxlJobExecutor registJobHandler break, not JobHandler.class find in srping container");
            return;
        }
        Arrays.stream(jobHandlerBeanNames).forEach(jobHandlerBeanName -> {
            try{
                JobHandler jobHandler = applicationContext.getBean(jobHandlerBeanName , JobHandler.class);
                if(jobHandler == null){
                    logger.warn("XxlJobExecutor registJobHandler break, bean:{} JobHandler.class not find", jobHandlerBeanName);
                    return;
                }
                logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", jobHandlerBeanName, jobHandler);
                jobHandlerRepository.put(jobHandlerBeanName, jobHandler);
            }catch (Exception e){
                logger.warn("XxlJobExecutor registJobHandler break, find bean:{} JobHandler.class error:{}", jobHandlerBeanName, e);
            }
        });
    }


    // ---------------------- job thread repository ----------------------
    private static ConcurrentMap<Long, JobThread> jobThreadRepository = new ConcurrentHashMap<Long, JobThread>();
    public static JobThread registJobThread(long jobId, JobHandler handler, String removeOldReason){

        JobThread newJobThread = new JobThread(jobId, handler);
        newJobThread.start();
        logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});

        JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);	// putIfAbsent | oh my god, map's put method return the old value!!!
        if (oldJobThread != null) {
            oldJobThread.toStop(removeOldReason);
            oldJobThread.interrupt();
        }

        return newJobThread;
    }

    public static JobThread removeJobThread(long jobId, String removeOldReason){
        JobThread oldJobThread = jobThreadRepository.remove(jobId);
        if (oldJobThread != null) {
            oldJobThread.toStop(removeOldReason);
            oldJobThread.interrupt();

            return oldJobThread;
        }
        return null;
    }

    public static JobThread loadJobThread(long jobId){
        return jobThreadRepository.get(jobId);
    }

    //*********************** getter and setter *************************
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public ExecutorProperties getExecutorProperties() {
        return executorProperties;
    }

    public void setExecutorProperties(ExecutorProperties executorProperties) {
        this.executorProperties = executorProperties;
    }
}
